package com.rizhov.graph;

import java.util.Random;

import com.rizhov.enumerations.GraphModelType;
import com.rizhov.enumerations.GraphOrientation;
import com.rizhov.graphmodels.GraphModel;
import com.rizhov.graphmodels.ListGraphModel;
import com.rizhov.graphmodels.MatrixGraphModel;

public class Graph {

    /* field for singleton */
    private static Graph uniqueInstance;

    /* graph model enumeration */
    private GraphModel graphModel;

    public GraphModel getGraphModel() {
	return graphModel;
    }

    private Graph() {
	graphModel = new ListGraphModel();
    }

    public Graph(int vertexCount, GraphOrientation orientation, GraphModelType modelType) {
	if (modelType == GraphModelType.MATRIX_MODEL_TYPE) {
	    graphModel = new MatrixGraphModel();
	} else {
	    graphModel = new ListGraphModel();
	}
	setGraphOrientation(orientation);
	Random r = new Random();
	int successfulCount = 0;
	while (successfulCount < vertexCount) {
	    int tempInt = r.nextInt(100);
	    for (int j = 0; j < vertexCount; j++) {
		if (graphModel.getVertexData(j).toString().equals(new Integer(tempInt).toString())) {
		    break;
		}
		if (j == getVertexCount() - 1) {
		    graphModel.addVertex(tempInt);
		    successfulCount++;
		}
	    }
	}
    }

    public Graph(int vertexCount, int edgeCount, GraphOrientation orientation,
	    GraphModelType modelType) {
	this(vertexCount, orientation, modelType);
	Random r = new Random();
	int successfulCount = 0;
	while (successfulCount < edgeCount) {
	    int tempInt = r.nextInt(100);
	    for (int j = 0; j < edgeCount; j++) {
		if (graphModel.getVertexData(j).toString().equals(new Integer(tempInt).toString())) {
		    break;
		}
		if (j == getEdgeCount() - 1) {
		    graphModel.addVertex(tempInt);
		    successfulCount++;
		}
	    }
	}
    }

    public synchronized static Graph getInstance() {
	if (uniqueInstance == null) {
	    uniqueInstance = new Graph();
	}
	return uniqueInstance;
    }

    public void setGraphOrientation(GraphOrientation graphOrientation) {
	graphModel.setGraphOrientation(graphOrientation);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
	Graph clone = new Graph();
	clone.graphModel = (GraphModel) graphModel.clone();
	return clone;
    }

    public void convertToListGraph() {
	if (graphModel.getGraphModelType() == GraphModelType.LIST_MODEL_TYPE) {
	    // do nothing, it's list graph yet
	} else {
	    GraphModel tempModel = new ListGraphModel();
	    tempModel.setGraphOrientation(GraphOrientation.ORIENTED_GRAPH);
	    for (int i = 0; i < graphModel.getVertexCount(); i++) {
		tempModel.addVertex(graphModel.getVertexData(i));
	    }
	    for (int i = 0; i < graphModel.getVertexCount(); i++) {
		for (int j = 0; j < graphModel.getVertexCount(); j++) {
		    if (graphModel.isEdgeExist(i, j)) {
			tempModel.addEdge(i, j, graphModel.getEdgeWeight(i, j));
			tempModel.setEdgeWeight(i, j, graphModel.getEdgeWeight(i, j));
			tempModel.setEdgeData(i, j, graphModel.getEdgeData(i, j));
		    }
		}
	    }
	    tempModel.setGraphOrientation(graphModel.getGraphDirectionType());
	    graphModel = tempModel;
	}
    }

    public void convertToMatrixGraph() {
	if (graphModel.getGraphModelType() == GraphModelType.MATRIX_MODEL_TYPE) {
	    // do nothing, it's matrix graph yet
	} else {
	    GraphModel tempModel = new MatrixGraphModel();
	    tempModel.setGraphOrientation(GraphOrientation.ORIENTED_GRAPH);
	    for (int i = 0; i < graphModel.getVertexCount(); i++) {
		tempModel.addVertex(graphModel.getVertexData(i));
	    }
	    for (int i = 0; i < graphModel.getVertexCount(); i++) {
		for (int j = 0; j < graphModel.getVertexCount(); j++) {
		    if (graphModel.isEdgeExist(i, j)) {
			tempModel.addEdge(i, j, graphModel.getEdgeWeight(i, j));
			tempModel.setEdgeWeight(i, j, graphModel.getEdgeWeight(i, j));
			tempModel.setEdgeData(i, j, graphModel.getEdgeData(i, j));
		    }
		}
	    }
	    tempModel.setGraphOrientation(graphModel.getGraphDirectionType());
	    graphModel = tempModel;
	}
    }

    public int getVertexCount() {
	return graphModel.getVertexCount();
    }

    public int getEdgeCount() {
	return graphModel.getEdgeCount();
    }

    public GraphOrientation getGraphDirectionType() {
	return graphModel.getGraphDirectionType();
    }

    public GraphModelType getGraphModelType() {
	return graphModel.getGraphModelType();
    }

    public double getGraphSaturationRatio() {
	return graphModel.getGraphSaturationRatio();
    }

    public boolean addVertex(Object vertexData) {
	return graphModel.addVertex(vertexData);
    }

    public int getVertexIndex(Object data) {
	return graphModel.getVertexIndex(data);
    }

    public boolean removeVertex(Object vertexData) {
	return graphModel.removeVertex(vertexData);
    }

    public boolean addEdge(int startIndex, int endIndex, Object edgeWeight) {
	return graphModel.addEdge(startIndex, endIndex, edgeWeight);
    }

    public boolean removeEdge(int startIndex, int endIndex) {
	return graphModel.removeEdge(startIndex, endIndex);
    }

    public boolean isEdgeExist(int startIndex, int endIndex) {
	return graphModel.isEdgeExist(startIndex, endIndex);
    }

    public boolean setEdgeWeight(int startIndex, int endIndex, Object weight) {
	return graphModel.setEdgeWeight(startIndex, endIndex, weight);
    }

    public Object getEdgeWeight(int startIndex, int endIndex) {
	return graphModel.getEdgeWeight(startIndex, endIndex);
    }

    public boolean setEdgeData(int startIndex, int endIndex, Object data) {
	return graphModel.setEdgeData(startIndex, endIndex, data);
    }

    public Object getEdgeData(int startIndex, int endIndex) {
	return graphModel.getEdgeData(startIndex, endIndex);
    }

    public String getStatisticsString() {
	StringBuilder sb = new StringBuilder();
	sb.append("----------------------------------------------------- \n");
	sb.append("Graph data: ----------------------------------------- \n");
	sb.append("Dirrection type: " + getGraphDirectionType().toString() + '\n');
	sb.append("Model type     : " + getGraphModelType().toString() + '\n');
	sb.append("Saturation   : " + getGraphSaturationRatio() + '\n');
	sb.append("Edge count   : ");
	if (getGraphDirectionType() == GraphOrientation.ORIENTED_GRAPH) {
	    sb.append(getEdgeCount());
	} else {
	    sb.append(getEdgeCount()/2);
	}
	sb.append('\n');
	sb.append("Vertex count : " + getVertexCount() + '\n');
	sb.append("----------------------------------------------------- \n");
	sb.append("Model statistics: ----------------------------------- \n");
	sb.append(graphModel.getStatisticsString() + '\n');
	return sb.toString();
    }

    public Object getVertexData(int index) {
	return graphModel.getVertexData(index);
    }

    public boolean setVertexData(Object newVertexData, Object oldVertexData) {
	return graphModel.setVertexData(newVertexData, oldVertexData);
    }
}
